package git.soulbgm;

import cn.smallbun.screw.core.Configuration;
import cn.smallbun.screw.core.engine.EngineConfig;
import cn.smallbun.screw.core.engine.EngineFileType;
import cn.smallbun.screw.core.engine.EngineTemplateType;
import cn.smallbun.screw.core.execute.DocumentationExecute;
import cn.smallbun.screw.core.process.ProcessConfig;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import javax.sql.DataSource;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.sql.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;

/**
 * 程序入口
 *
 * @author SoulBGM
 * @date 2021/02/20
 */
public class Application {

    /**
     * 模板配置路径
     */
    private static final String CONFIG_PATH = "config/config.properties";

    private static final LogUtil log = new LogUtil(Application.class);

    /**
     * 文件是否存在
     *
     * @param file
     * @return
     */
    public static boolean exist(File file) {
        if (file != null) {
            return file.exists();
        }
        return false;
    }

    /**
     * 判断1个或多个字符串是否为空
     * 判断条件 null 和 长度不为0
     *
     * @param str 字符串数组
     * @return 一个或多个为空返回true 否则false
     */
    public static boolean isEmpty(String... str) {
        for (String s : str) {
            if (s == null) {
                return true;
            }
            if (s.trim().length() == 0) {
                return true;
            }
        }
        return false;
    }

    /**
     * 转换字符串为小写
     *
     * @param source 源字符串
     * @return 转换之后的字符串
     */
    public static String toLowerCase(String source) {
        if (isEmpty(source)) {
            return null;
        }
        return source.toLowerCase();
    }

    /**
     * 通过禁用访问安全检查来获得字段的值
     *
     * @param obj   bean对象
     * @param field 字段名称
     * @param val   设置字段的值
     */
    public static void setFieldVal(Object obj, Field field, Object val) throws IllegalAccessException {
        // 禁用访问安全检查
        field.setAccessible(true);
        field.set(obj, val);
    }

    /**
     * 动态加载jar
     *
     * @param jarPath jar的路径
     */
    public static void loadJar(String jarPath) {
        File jarFile = new File(jarPath);
        // 从URLClassLoader类中获取类所在文件夹的方法，jar也可以认为是一个文件夹
        Method method = null;
        try {
            method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
        } catch (NoSuchMethodException | SecurityException e1) {
            e1.printStackTrace();
        }
        // 获取方法的访问权限以便写回
        boolean accessible = method.isAccessible();
        try {
            method.setAccessible(true);
            // 获取系统类加载器
            URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
            URL url = jarFile.toURI().toURL();
            method.invoke(classLoader, url);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            method.setAccessible(accessible);
        }
    }

    /**
     * 加载配置
     */
    private static void loadConfigure() {
        String configPath = CONFIG_PATH;
        String path = Application.class.getProtectionDomain().getCodeSource().getLocation().getPath();
        if (PropertiesUtil.isJarURL(configPath)) {
            path = path.substring(0, path.lastIndexOf(PropertiesUtil.LEFT_SLANTING_BAR));
            configPath = path + File.separator + "config.properties";
            if (!exist(new File(configPath))) {
                log.error("config.properties文件不存在");
                System.exit(0);
            }
        }
        Map<String, String> map = PropertiesUtil.getPrefixProperties(configPath, "config", "#");
        SysConfig config = SysConfig.getConfig();
        Field[] fields = config.getClass().getDeclaredFields();
        for (Field field : fields) {
            String val = map.get(field.getName());
            if (val == null || "".equals(val.trim())) {
                continue;
            }
            Object value = val;
            if (field.getType() == String[].class) {
                String[] splitVal = val.split(",");
                for (int i = 0; i < splitVal.length; i++) {
                    splitVal[i] = toLowerCase(splitVal[i].trim());
                }
                value = splitVal;
            }
            if (field.getType() == Boolean.class) {
                value = Boolean.parseBoolean(val);
            }
            try {
                setFieldVal(config, field, value);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        if (check(config)) {
            log.error("必填项没有填写");
            System.exit(0);
        }
        if (isEmpty(config.getFileOutputDir())) {
            config.setFileOutputDir(path);
        }
        if (isEmpty(config.getFileOutputType())) {
            config.setFileOutputType("html");
        }
        if (isEmpty(config.getDocFileName())) {
            config.setDocFileName("DatabaseDescription");
        }
        if (isEmpty(config.getDocVersion())) {
            config.setDocVersion("1.0");
        }
        loadJar(config.getDatabaseDriverPath());
    }

    private static boolean check(SysConfig config) {
        return isEmpty(config.getDatabaseDriverPath(), config.getDatabaseDriver(), config.getDatabaseUrl(), config.getDatabaseUsername(), config.getDatabasePassword());
    }

    public static void main(String[] args) {
        loadConfigure();
        SysConfig sysConfig = SysConfig.getConfig();
        // 创建 screw 的配置
        Configuration config = Configuration.builder()
                // 版本
                .version(sysConfig.getDocVersion())
                // 描述
                .description(sysConfig.getDocDescription())
                // 数据源
                .dataSource(buildDataSource())
                // 引擎配置
                .engineConfig(buildEngineConfig())
                // 处理配置
                .produceConfig(buildProcessConfig())
                .build();

        // 执行 screw，生成数据库文档
        new DocumentationExecute(config).execute();
    }

    /**
     * 创建数据源
     */
    private static DataSource buildDataSource() {
        SysConfig config = SysConfig.getConfig();
        // 创建 HikariConfig 配置类
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName(config.getDatabaseDriver());
        hikariConfig.setJdbcUrl(config.getDatabaseUrl());
        hikariConfig.setUsername(config.getDatabaseUsername());
        hikariConfig.setPassword(config.getDatabasePassword());
        // 设置可以获取 tables remarks 信息
        hikariConfig.addDataSourceProperty("useInformationSchema", "true");
        // 创建数据源
        return new HikariDataSource(hikariConfig);
    }

    /**
     * 创建 screw 的引擎配置
     */
    private static EngineConfig buildEngineConfig() {
        SysConfig config = SysConfig.getConfig();
        EngineFileType fileOutputType = EngineFileType.HTML;
        if ("word".equalsIgnoreCase(config.getFileOutputType())) {
            fileOutputType = EngineFileType.WORD;
        } else if ("md".equalsIgnoreCase(config.getFileOutputType())) {
            fileOutputType = EngineFileType.MD;
        }
        return EngineConfig.builder()
                // 生成文件路径
                .fileOutputDir(config.getFileOutputDir())
                // 打开目录
                .openOutputDir(false)
                // 文件类型
                .fileType(fileOutputType)
                // 文件类型
                .produceType(EngineTemplateType.freemarker)
                // 自定义文件名称
                .fileName(config.getDocFileName())
                .build();
    }

    /**
     * 创建 screw 的处理配置，一般可忽略
     * 指定生成逻辑、当存在指定表、指定表前缀、指定表后缀时，将生成指定表，其余表不生成、并跳过忽略表配置
     */
    private static ProcessConfig buildProcessConfig() {
        SysConfig config = SysConfig.getConfig();
        ProcessConfig.ProcessConfigBuilder builder = ProcessConfig.builder();
        // 根据名称指定表生成
        builder.designatedTableName(Collections.emptyList());
        // 根据表前缀生成
        builder.designatedTablePrefix(Collections.emptyList());
        // 根据表后缀生成
        builder.designatedTableSuffix(Collections.emptyList());
        if (config.getIgnoreTableName() != null) {
            // 忽略表名
            builder.ignoreTableName(Arrays.asList(config.getIgnoreTableName()));
        }
        if (config.getIgnoreTablePrefix() != null) {
            // 忽略表前缀
            builder.ignoreTablePrefix(Arrays.asList(config.getIgnoreTablePrefix()));
        }
        if (config.getIgnoreTableSuffix() != null) {
            // 忽略表后缀
            builder.ignoreTableSuffix(Arrays.asList(config.getIgnoreTableSuffix()));
        }
        return builder.build();
    }

}
